#pragma warning(disable : 4355)
#include "Avalon/IO/BufferedReader.hpp"
#include "Avalon/Threading/LockRelease.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Threading;
using namespace boost;

BufferedReader::BufferedReader(Carry<Reader> source)
    : m_source(source),
      m_writer(&m_reader),
      m_readSlot(bind(&BufferedReader::OnRead, this)),
      m_isBuffering(false) {}

BufferedReader::~BufferedReader() {
  m_writer.Break();
  m_reader.Break();
}

void BufferedReader::Read(Buffer* destination, Out<Async<int> > sizeRead) {
  {
    unique_lock<mutex> lock(m_mutex);
    EnableBuffering(lock);
  }
  m_reader.Read(destination, Store(sizeRead));
}

void BufferedReader::Read(void* destination, int size,
    Out<Async<int> > sizeRead) {
  {
    unique_lock<mutex> lock(m_mutex);
    EnableBuffering(lock);
  }
  m_reader.Read(destination, size, Store(sizeRead));
}

void BufferedReader::Read(Buffer* destination, int size,
    Out<Async<int> > sizeRead) {
  {
    unique_lock<mutex> lock(m_mutex);
    EnableBuffering(lock);
  }
  m_reader.Read(destination, size, Store(sizeRead));
}

void BufferedReader::EnableBuffering(unique_lock<mutex>& lock) {
  if(m_isBuffering) {
    return;
  }
  m_isBuffering = true;
  BufferReads(lock);
}

void BufferedReader::BufferReads(unique_lock<mutex>& lock) {
  Async<int>::State state = Async<int>::COMPLETE;
  while(state == Async<int>::COMPLETE) {
    m_source->Read(&m_destination, Store(m_sizeRead));
    m_sizeRead.SetFinishedSlot(m_readSlot, Store(state));
    if(state == Async<int>::EXCEPTION) {
      exception_ptr exceptionCopy = m_sizeRead.GetException();
      m_sizeRead.Reset();
      m_destination.Reset();
      lock.unlock();
      m_reader.Break(exceptionCopy);
      return;
    } else if(state == Async<int>::COMPLETE) {
      int sizeRead = m_sizeRead.Get();
      m_sizeRead.Reset();
      Buffer data = m_destination;
      m_destination.Reset();
      LockRelease<unique_lock<mutex> > release(lock);
      Async<void> writeResult;
      m_writer.Write(data.GetData(), sizeRead, Store(writeResult));
    }
  }
}

void BufferedReader::OnRead() {
  unique_lock<mutex> lock(m_mutex);
  if(m_sizeRead.GetState() == Async<int>::EXCEPTION) {
    exception_ptr exceptionCopy = m_sizeRead.GetException();
    m_sizeRead.Reset();
    m_destination.Reset();
    lock.unlock();
    m_reader.Break(exceptionCopy);
    return;
  } else {
    int sizeRead = m_sizeRead.Get();
    m_sizeRead.Reset();
    Buffer data = m_destination;
    m_destination.Reset();
    LockRelease<unique_lock<mutex> > release(lock);
    Async<void> writeResult;
    m_writer.Write(data.GetData(), sizeRead, Store(writeResult));
  }
  BufferReads(lock);
}
